using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using Spring.Util;

namespace CoreIMSEE.Core.Common
{
	public class PathParser: IEnumerable, IEnumerator 
	{
		#region Static fields

		private static IDictionary aliases = new Hashtable();
		private static IList prefixes = new ArrayList();
		private static IList postfixes = new ArrayList();

		static PathParser()
		{
			aliases.Add("string", typeof(string));

			prefixes.Add("CoreIMSEE.Domain.");

			postfixes.Add(", CoreIMSEE");
		}

		#endregion


		private string fullPath;
		private Scope scope;
		private Type keyType;
		private StringBuilder key;
		private StringBuilder path;
		private ScopePath current=ScopePath.Default;

		public PathParser(string path)
		{
			this.fullPath=path;
			Reset();
		}

		
		public IEnumerator GetEnumerator()
		{
			return this;
		}

		public bool MoveNext()
		{
			if (path.Length==0) return false;
			key.Append(DetachToken(path));
			current=ScopePath.Default; // we don't create current ScopePath in advance to, probably, save some resources
			return true;			
		}

		public void Reset()
		{
			keyType=null;
			path=new StringBuilder(fullPath);
			try
			{
				string scope=DetachToken(path).ToString();
				this.scope=(Scope)Enum.Parse(typeof(Scope), scope, true);	
			}
			catch (Exception e)
			{
				throw new ArgumentException(String.Format("Unknown Scope in the ScopePath {0}", fullPath), e);
			}
			if (path[0]=='.')
			{
				path.Remove(0,1);
			}

			StringBuilder token=GetToken(path);
			if (IsTypeToken(token))
			{
				path.Remove(0, token.Length);	
				this.keyType=ResolveType(token.ToString());
			}
			key=new StringBuilder(path.Length);
		}

		public object Current
		{
			get
			{
				EnsureCurrentScopePath();
				return current;
			}
		}
		private void EnsureCurrentScopePath()
		{
			if (!current.Equals(ScopePath.Default))
			{
				return;
			}
			
			
			StringBuilder indexerToken=GetToken(path);
			if (indexerToken==null||!IsIndexerToken(indexerToken))
			{
				current=new ScopePath(scope, keyType, key.ToString(), null, null, StripFirstDot(path.ToString()));		
				return;
			}
				
			string currentPath=this.path.Length>indexerToken.Length?this.path.ToString(indexerToken.Length, this.path.Length-indexerToken.Length): String.Empty;
			object[] indexerTokenInfo=ParseIndexerToken(indexerToken.ToString());
			current=new ScopePath(scope, keyType, key.ToString(), (Type)indexerTokenInfo[0], indexerTokenInfo[1], StripFirstDot(currentPath));							
		}

		private object[] ParseIndexerToken(string indexerToken)
		{	 
			StringBuilder sb=new StringBuilder (indexerToken);

			sb.Remove(0,1);
			sb.Remove(sb.Length-1, 1);

			StringBuilder typeToken=GetToken(sb);
			Type collectionItemType=null;
			if (IsTypeToken(typeToken))
			{
				collectionItemType=ResolveType(typeToken.ToString());
				sb.Remove(0, typeToken.Length);
			}			
			return new object[]{collectionItemType, ParseIndex(sb)};
		}

		private string StripFirstDot(string str)
		{
			if (str==null||str.Length==0)
			{
				return null;
			}
			if (str[0]=='.')
			{
				return str.Length>1?str.Substring(1, str.Length-1):null;
			}
			return str;
		}
		private object ParseIndex(StringBuilder token)
		{
			string str=token.ToString();
			str=str.Trim();
			if (str[0]=='\'')
			{
				return StringUtils.StripFirstAndLastCharacter(str);
			}
			return Int32.Parse(str);
		}
		private bool IsTypeToken(StringBuilder token)
		{
			return (token[0]=='{'&&token[token.Length-1]=='}');
		}
		private bool IsIndexerToken(StringBuilder token)
		{
			return Regex.IsMatch(token.ToString(), "^\\[(\\{.*\\})?((\\d*)|(\\'.*\\'))\\]$");
//			return (token[0]=='['&&token[token.Length-1]==']');
		}
		private StringBuilder DetachToken(StringBuilder expression)
		{
			StringBuilder token=GetToken(expression);
			expression.Remove(0, token.Length);
			return token;
		}
		private StringBuilder GetToken(StringBuilder expression)
		{
			StringBuilder sb = new StringBuilder();
			StringBuilder token=null;
			int pos = 0;
			char c;

			while (token==null&&pos<expression.Length)
			{
				c = expression[pos++];
				while (!IsNodeSeparator(c) && pos < expression.Length)
				{
					sb.Append(c);
					c = expression[pos++];
				}
				if (pos == expression.Length || c == '.')  // end of expression 
				{
					if (!IsNodeSeparator(c)||sb.Length==0)
					{
						sb.Append(c);
					}
//					sb.Append(c);
					if (sb.Length > 1||sb[0]!='.')
					{
						token = sb;
					}
//					if (sb.Length > 0)
//					{
//						token = sb;
//					}
				}
				else if (c == '[')      // indexer
				{
					if (sb.Length > 0) // property indexer, add property first
					{
						token=sb;
					} 
					else
					{
						while (c != ']' && pos < expression.Length)
						{
							sb.Append(c);
							c = expression[pos++];
						}
						sb.Append(c);
						token = sb;
					}
				}
				else if (c == '{')      // type identifier
				{
					while (c != '}' && pos < expression.Length)
					{
						sb.Append(c);
						c = expression[pos++];
					}
					sb.Append(c);
					token = sb;
				}
			}
			return token;
		}

		private bool IsNodeSeparator(char c)
		{
			return (".[{".IndexOf(c) >= 0);
		}


		#region Type resolving
		private Type ResolveType(string typeName)
		{
			//Strip curly brackets
			typeName=StringUtils.StripFirstAndLastCharacter(typeName);
		
			Type type=null;
			if(CheckIsFullType(typeName))
			{

				type = Type.GetType(typeName);

				if(type == null)
				{
					throw new TypeLoadException(String.Format("Unable to load Type {0} specified in ScopePath.", typeName));
				}
			}

			type = ProbeWellKnownType(typeName);
			if(type != null)
			{
				return type;
			}

			type = ProbeAliases(typeName);
			if(type != null)
			{
				return type;
			}
			
			type = ProbeCoreIMSEEKnownType(typeName);
			if(type != null)
			{
				return type;
			}

			throw new TypeLoadException(String.Format("Unable to load Type {0} specified in ScopePath.", typeName));
		}

		private bool CheckIsFullType(string typeName)
		{
			return typeName.IndexOf(',') > 0;
		}

		private Type ProbeWellKnownType(string typeName)
		{
			return Type.GetType(typeName);
		}

		private Type ProbeAliases(string typeName)
		{
			return aliases[typeName] as Type;
		}

		private Type ProbeCoreIMSEEKnownType(string typeName)
		{
			string tmpTypeName;
			foreach(string postfix in postfixes)
			{
				tmpTypeName = typeName + postfix;
				Type tmpType = Type.GetType(tmpTypeName);
				if(tmpType != null)
				{
					return tmpType;
				}
			}

			foreach(string prefix in prefixes)
			{
				foreach(string postfix in postfixes)
				{
					tmpTypeName = prefix + typeName + postfix;
					Type tmpType = Type.GetType(tmpTypeName);
					if(tmpType != null)
					{
						return tmpType;
					}
				}
			}
			return null;
		}

		#endregion

	}
}
